#ifndef __LOG_HPP__
#define __LOG_HPP__

#include <iostream>
#include <cstdio>
#include <string>
#include <filesystem> //C++17
#include <sstream>
#include <fstream>
#include <ctime>
#include <memory>
#include <unistd.h>
#include "Mutex.hpp"

namespace LogModule
{
    using namespace MutexModule;

    const std::string gsep = "\r\n";
    // 策略模式 -- 利用C++的多态特性
    // 1. 刷新策略 a: 向显示器打印 b: 向文件中写入
    // 刷新策略基类
    class LogStrategy
    {
    public:
        virtual ~LogStrategy() = default;
        virtual void SyncLog(const std::string &message) = 0;
    };

    // 显示器打印日志的策略
    class ConsoleLogStrategy : public LogStrategy
    {
    public:
        ConsoleLogStrategy()
        {
        }

        void SyncLog(const std::string &message) override
        {
            // 加锁使多线程原子性的访问显示器
            LockGuard lockGuard(_mutex);
            std::cout << message << gsep;
        }

        ~ConsoleLogStrategy()
        {
        }

    private:
        Mutex _mutex;
    };

    // 文件打印日志策略
    // 默认的日志文件路径和日志文件名
    const std::string defaultPath = "./log";
    const std::string defaultFile = "my.log";
    class FileLogStrategy : public LogStrategy
    {
    public:
        FileLogStrategy(const std::string &path = defaultPath, const std::string &file = defaultFile)
            : _path(path),
              _file(file)
        {
            // 加锁使多线程原子性的访问文件
            LockGuard lockGuard(_mutex);
            // 判断目录是否存在
            if (std::filesystem::exists(_path)) // 检测文件系统对象（文件，目录，符号链接等）是否存在
            {
                return;
            }
            try
            {
                // 如果目录不存在，递归创建目录
                std::filesystem::create_directories(_path);
            }
            catch (const std::filesystem::filesystem_error &e) // 如果创建失败则打印异常信息
            {
                std::cerr << e.what() << '\n';
            }
        }

        void SyncLog(const std::string &message) override
        {
            LockGuard lockGuard(_mutex);
            // 追加方式向文件中写入
            std::string fileName = _path + (_path.back() == '/' ? "" : "/") + _file;
            // std::ofstream是C++标准库中用于输出到文件的流类，主要用于将数据写入文件
            std::ofstream out(fileName, std::ios::app);
            if (!out.is_open())
            {
                return;
            }
            out << message << gsep;
            out.close();
        }

        ~FileLogStrategy()
        {
        }

    private:
        std::string _path; // 日志文件所在路径
        std::string _file; // 日志文件本身
        Mutex _mutex;
    };

    // 2. 形成完整日志并刷新到指定位置
    // 2.1 日志等级
    enum class LogLevel
    {
        DEBUG,
        INFO,
        WARNING,
        ERROR,
        FATAL
    };

    // 2.2 枚举类型的日志等级转换为字符串类型
    std::string Level2Str(LogLevel level)
    {
        switch (level)
        {
        case LogLevel::DEBUG:
            return "DEBUG";
        case LogLevel::INFO:
            return "INFO";
        case LogLevel::WARNING:
            return "WARNING";
        case LogLevel::ERROR:
            return "ERROR";
        case LogLevel::FATAL:
            return "FATAL";
        default:
            return "UNKNOWN";
        }
    }

    // 2.3 获取当前时间的函数
    std::string GetCurTime()
    {
        // time 函数参数为一个time_t类型的指针，若该指针不为NULL，会把获取到的当前时间值存储在指针指向的对象中
        // 若传入为NULL，则仅返回当前时间，返回从1970年1月1日0点到目前的秒数
        time_t cur = time(nullptr);
        struct tm curTm;
        // localtime_r是localtime的可重入版本，主要用于将time_t类型表示的时间转换为本地时间，存储在struct tm 结构体中
        localtime_r(&cur, &curTm);
        char timeBuffer[128];
        snprintf(timeBuffer, sizeof(timeBuffer), "%4d-%02d-%02d %02d:%02d:%02d",
                 curTm.tm_year + 1900,
                 curTm.tm_mon + 1,
                 curTm.tm_mday,
                 curTm.tm_hour,
                 curTm.tm_min,
                 curTm.tm_sec);
        return timeBuffer;
    }

    // 2.4 日志形成并刷新
    class Logger
    {
    public:
        // 默认刷新到显示器上
        Logger()
        {
            EnableConsoleLogStrategy();
        }

        void EnableConsoleLogStrategy()
        {
            // std::make_unique用于创建并返回一个std::unique_ptr对象
            _fflushStrategy = std::make_unique<ConsoleLogStrategy>();
        }

        void EnableFileLogStrategy()
        {
            _fflushStrategy = std::make_unique<FileLogStrategy>();
        }
        //  内部类默认是外部类的友元类，可以访问外部类的私有成员变量
        //  内部类LogMessage，表示一条日志信息的类
        class LogMessage
        {
        public:
            LogMessage(LogLevel &level, std::string &srcName, int lineNum, Logger &logger)
                : _curTime(GetCurTime()),
                  _level(level),
                  _pid(getpid()),
                  _srcName(srcName),
                  _lineNum(lineNum),
                  _logger(logger)
            {
                // 日志的基本信息合并起来
                // std::stringstream用于在内存中进行字符串的输入输出操作, 提供一种方便的方式处理字符串
                // 将不同类型的数据转换为字符串，也可以将字符串解析为不同类型的数据
                std::stringstream ss;
                ss << "[" << _curTime << "] "
                   << "[" << Level2Str(_level) << "] "
                   << "[" << _pid << "] "
                   << "[" << _srcName << "] "
                   << "[" << _lineNum << "] "
                   << "- ";

                _logInfo = ss.str();
            }

            //  使用模板重载运算符<< -- 支持不同数据类型的输出运算符重载
            template <typename T>
            LogMessage &operator<<(const T &info)
            {
                std::stringstream ss;
                ss << info;
                _logInfo += ss.str();
                return *this;
            }

            ~LogMessage()
            {
                if (_logger._fflushStrategy)
                {
                    _logger._fflushStrategy->SyncLog(_logInfo);
                }
            }

        private:
            std::string _curTime;   // 日志时间
            LogLevel _level;    // 日志等级
            pid_t _pid; // 进程pid
            std::string _srcName;   // 输出日志的文件名
            int _lineNum;   //输出日志的行号
            std::string _logInfo;   //完整日志内容
            Logger &_logger;    // 方便使用策略进行刷新
        };

        // 使用宏进行替换之后调用的形式如下
        // logger(level, __FILE__, __LINE__) << "hello world" << 3.14;
        // 这里使用仿函数的形式，调用LogMessage的构造函数，构造一个匿名的LogMessage对象
        // 返回的LogMessage对象是一个临时对象，它的生命周期从创建开始到包含它的完整表达式结束(可以简单理解为包含
        // 这个对象的该行代码)
        // 代码调用结束的时候，如果没有LogMessage对象进行临时对象的接收，则会调用析构函数,
        // 如果有LogMessage对象进行临时对象的接收，会调用拷贝构造或者移动构造构造一个对象，并析构临时对象
        // 所以通过临时变量调用析构函数进行日志的打印
        LogMessage operator()(LogLevel level, std::string name, int line)
        {
            return LogMessage(level, name, line, *this);
        }

        ~Logger()
        {
        }

    private:
        std::unique_ptr<LogStrategy> _fflushStrategy;
    };

    //  定义一个全局的Logger对象
    Logger logger;

    // 使用宏定义，简化用户操作并且获取文件名和行号
    #define LOG(level) logger(level, __FILE__, __LINE__) // 使用仿函数的方式进行调用
    #define Enable_Console_Log_Strategy() logger.EnableConsoleLogStrategy()
    #define Enable_File_Log_Strategy() logger.EnableFileLogStrategy()
}

#endif